package ua.khpi.si.kit49.diploma.service;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;

import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import ua.khpi.si.kit49.diploma.core.RepliedRoute;
import ua.khpi.si.kit49.diploma.core.Route;
import ua.khpi.si.kit49.diploma.core.Path;
import ua.khpi.si.kit49.diploma.core.Weather;
import ua.khpi.si.kit49.diploma.db.dto.City;
import ua.khpi.si.kit49.diploma.db.dto.Connection;

/**
 * Implementation of route service
 * 
 * @author netstalk3r
 * 
 */

@Service("routeService")
public class RouteServiceImpl implements RouteService {

	private static final Logger log = Logger.getLogger(RouteServiceImpl.class);

	private static final Integer AMOUNT_OF_ROUTES_TO_RETURN = 4;

	@Autowired(required = true)
	private PathService pathService;

	@Autowired(required = true)
	private ConnectionService connectionService;

	@Autowired(required = true)
	RoadPriorityService roadPriorityService;

	@Autowired(required = true)
	RoadQualityService roadQualityService;

	@Override
	public Set<Route> getBestRoutes(Integer start, Integer finish) {
		log.info("Route Service - start!");
		Set<Route> bestRoutes = new TreeSet<Route>();
		CityHolder cityHolder = CityHolder.getInstance();
		List<Path> paths = pathService.findPaths(cityHolder.getAmountOfCity(),
				start, finish);
		Iterator<Path> iterator = paths.iterator();
		for (int i = 0; i < AMOUNT_OF_ROUTES_TO_RETURN; i++) {
			Route route = new Route();
			route.setPath(iterator.next());
			calculateRoute(route, cityHolder);
			bestRoutes.add(route);
		}
		log.info("Route Service - finish!");
		return bestRoutes;
	}

	@Transactional(propagation = Propagation.REQUIRED, readOnly = true)
	private Route calculateRoute(final Route route, CityHolder cityHolder) {
		log.info("start calculating features");
		Path path = route.getPath();
		Integer pathSize = path.getLenght();
		log.debug("Lenght of path - " + pathSize);
		List<Connection> conns = new ArrayList<Connection>(pathSize);
		Integer lengthRoute = 0;
		for (int i = 0; i + 1 < pathSize; i++) {
			Integer start = path.getPoint(i);
			Integer finish = path.getPoint(i + 1);
			log.debug("Connection: start - " + start + "; finish - " + finish);
			Connection conn = connectionService.getConnectionByStartAndFinish(
					start, finish);
			log.debug("Connection id = " + conn.getId());
			lengthRoute += conn.getConnectionFeature().getDistance();
			conns.add(conn);
		}
		route.setLengthRoute(lengthRoute);
		List<City> cities = new ArrayList<City>();
		for (int i = 0; i < pathSize; i++) {
			cities.add(cityHolder.getCityById(path.getPoint(i)));
		}
		route.setCities(cities);
		log.info("Total lenght of route - " + route.getLengthRoute());
		calculateAverageRoadPriority(conns, route);
		log.info("Average priority of road - "
				+ route.getAverageRoadPriority().getPriority());
		calculateAverageFlowIntensity(conns, route);
		log.info("Average flow intensity - " + route.getAverageFlowIntensity());
		calculateAverageRoadQuality(conns, route);
		log.info("Average quality of raod - "
				+ route.getAverageRoadQuality().getQuality());
		calculateAverageRoadWidth(conns, route);
		log.info("Average road width - " + route.getAverageRoadWidth());
		calculateAverageWeather(route);
		log.info("Average weather for comparing: temperature - "
				+ route.getAverageWeather().getTemperature()
				+ "; wind speed - " + route.getAverageWeather().getWindSpeed());
		log.info("stop calculating features");
		return route;
	}

	@Transactional(propagation = Propagation.REQUIRED, readOnly = true)
	private void calculateAverageRoadPriority(List<Connection> conns,
			Route route) {
		int id = 0;
		for (int i = 0; i < conns.size(); i++) {
			id += conns.get(i).getConnectionFeature().getRoadPriority().getId();
		}
		id = Math.round(id / conns.size());
		route.setAverageRoadPriority(roadPriorityService
				.getRoadPriorityById(id));
	}

	private void calculateAverageFlowIntensity(List<Connection> conns,
			Route route) {
		Integer averageFlow = 0;
		for (int i = 0; i < conns.size(); i++) {
			averageFlow += conns.get(i).getConnectionFeature()
					.getFlowIntensity();
		}
		averageFlow = ((averageFlow / conns.size()) / 100) * 100;
		route.setAverageFlowIntensity(averageFlow);
	}

	@Transactional(propagation = Propagation.REQUIRED, readOnly = true)
	private void calculateAverageRoadQuality(List<Connection> conns, Route route) {
		int id = 0;
		for (int i = 0; i < conns.size(); i++) {
			id += conns.get(i).getConnectionFeature().getRoadQuality().getId();
		}
		id = Math.round(id / conns.size());
		route.setAverageQuality(roadQualityService.getRoadQualityById(id));
	}

	private void calculateAverageRoadWidth(List<Connection> conns, Route route) {
		Integer averageWidth = 0;
		for (int i = 0; i < conns.size(); i++) {
			averageWidth += conns.get(i).getConnectionFeature().getRoadWidth();
		}
		averageWidth = Math.round(averageWidth / conns.size());
		route.setAverageRoadWidth(averageWidth);
	}

	private void calculateAverageWeather(Route route) {
		Integer averageTemperature = 0;
		Double averageSpeedOfWind = 0D;
		Path path = route.getPath();
		List<City> cities = route.getCities();
		for (int i = 0; i < cities.size(); i++) {
			averageTemperature += cities.get(i).getWeather().getTemperature();
			averageSpeedOfWind += cities.get(i).getWeather().getWindSpeed();
		}
		averageTemperature = Math.round(averageTemperature / path.getLenght());
		averageSpeedOfWind = averageSpeedOfWind / path.getLenght();
		averageSpeedOfWind = Math.round(averageSpeedOfWind * 100.0) / 100.0;
		Weather weather = new Weather();
		weather.setTemperature(averageTemperature);
		weather.setWindSpeed(averageSpeedOfWind);
		route.setAverageWeather(weather);
	}

	@Override
	public List<RepliedRoute> convertToRepliedRoutes(Set<Route> routes) {
		List<RepliedRoute> listRoutes = new ArrayList<RepliedRoute>(3);
		Iterator<Route> iterator = routes.iterator();
		while (iterator.hasNext()) {
			Route route = iterator.next();
			RepliedRoute repliedRoute = new RepliedRoute();
			List<City> cities = route.getCities();
			List<String> citiesNames = new ArrayList<String>();
			for (int i = 0; i < cities.size(); i++) {
				citiesNames.add(cities.get(i).getName());
			}
			repliedRoute.setCitiesNames(citiesNames);
			repliedRoute.setLengthRoute(route.getLengthRoute());
			repliedRoute.setRoadPriority(route.getAverageRoadPriority()
					.getPriority());
			repliedRoute.setFlowIntensity(route.getAverageFlowIntensity());
			repliedRoute.setRoadQuality(route.getAverageRoadQuality()
					.getQuality());
			repliedRoute.setRoadWidth(route.getAverageRoadWidth());
			repliedRoute.setWindSpeed(route.getAverageWeather().getWindSpeed());
			repliedRoute.setTemperature(route.getAverageWeather()
					.getTemperature());
			listRoutes.add(repliedRoute);
		}
		return listRoutes;
	}

}
